﻿// implementation of .NET 4.0 dynamic object over Node/Edge data model

// provides a read-only object-based view over M values

namespace Microsoft.M
{

    using System.Reflection;

    using System.Dataflow;

    using System.IO;

    using System.Dynamic;

    using System;

    using System.Collections;

    using System.Collections.Generic;



    public class DynamicObjectNode : DynamicObject
    {

        Node node;


        // we never want to double-wrap, so all construction

        // goes through NodeToObject, which unwraps atomics

        internal DynamicObjectNode(Node node)
        {

            this.node = node;

        }


        // this method is the only way to go from the low-level

        // node world to the world of (dynamic) objects

        public static object NodeToObject(Node node)
        {

            if (node.NodeKind == NodeKind.Atomic)

                return node.AtomicValue;

            else

                return new DynamicObjectNode(node);

        }


        public static IEnumerable<dynamic> ReadValues(TextReader reader)
        {

            foreach (var edge in Node.ReadFrom(reader))
            {

                yield return NodeToObject(edge.Node);

            }

        }

        public static IEnumerable<dynamic> ReadValuesFromString(string text)
        {

            foreach (var edge in Node.ReadFromString(text))
            {

                yield return NodeToObject(edge.Node);

            }

        }


        // implement the obvious three...

        public override bool Equals(object obj)
        {

            return node.Equals(obj);

        }


        public override int GetHashCode()
        {

            return node.GetHashCode();

        }


        public override string ToString()
        {

            return node.ToString();

        }



        public Identifier GetBrand() { return node.Brand; }


        // special case conversion to IEnumerable to support foreach

        // over lists and collections

        public override bool TryConvert(ConvertBinder binder, out object result)
        {

            result = null;

            if (binder.Type != typeof(IEnumerable))

                return false;


            switch (node.NodeKind)
            {

                case NodeKind.Collection:

                    result = WrapCollectionNodeAsEnumerable(node);

                    return true;

                case NodeKind.List:

                    result = WrapListNodeAsEnumerable(node);

                    return true;

                default:

                    return false;


            }

        }

        IEnumerable WrapCollectionNodeAsEnumerable(Node node)
        {

            foreach (var item in node.Edges)
            {

                yield return NodeToObject(item.Node);

            }

        }

        IEnumerable WrapListNodeAsEnumerable(Node node)
        {

            foreach (var item in node.ViewAsList())
            {

                yield return NodeToObject(item);

            }

        }



        // support [int] over list and [string] over records

        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {

            result = null;

            if (indexes.Length > 1)

                return false;

            if (node.NodeKind == NodeKind.List && indexes[0] is int)
            {

                int index = (int)indexes[0];

                var list = node.ViewAsList();

                if (index >= list.Count)

                    throw new IndexOutOfRangeException("Indexed past end of M list node");

                result = NodeToObject(list[index]);

                return true;

            }

            string name = indexes[0] as string;

            if (name != null)
            {

                return TryGetMember(name, true, out result);

            }

            return false;

        }


        // support . over records and .Count/.IsReadOnly over collections/lists

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {

            return TryGetMember(binder.Name, binder.IgnoreCase, out result);

        }


        bool TryGetMember(string name, bool ignoreCase, out object result)
        {

            result = null;

            switch (node.NodeKind)
            {

                case NodeKind.List:

                    if (name == "Count")
                    {

                        result = node.ViewAsList().Count;

                        return true;

                    }

                    else if (name == "IsReadOnly")
                    {

                        result = true;

                        return true;

                    }

                    return false;

                case NodeKind.Collection:

                    if (name == "Count")
                    {

                        result = node.Edges.Count;

                        return true;

                    }

                    else if (name == "IsReadOnly")
                    {

                        result = true;

                        return true;

                    }

                    return false;

                case NodeKind.Record:

                    if (node.Edges.ContainsLabel(name))
                    {

                        result = NodeToObject(node.Edges[name]);

                        return true;

                    }

                    else if (ignoreCase)
                    {

                        foreach (var e in node.Edges)
                        {

                            if (string.Compare(e.Label.Text, name, StringComparison.InvariantCultureIgnoreCase) == 0)
                            {

                                result = NodeToObject(e.Node);

                                return true;

                            }

                        }

                    }

                    return false;

                default:

                    return false;


            }

        }

    }


    public class Language
    {

        MImage image;

        GraphStore store = new DefaultGraphStore();

        string moduleName, languageName;

        ParserFactory factory;


        internal Language(MImage image, string moduleName, string languageName)
        {

            this.image = image;

            this.moduleName = moduleName;

            this.languageName = languageName;

            this.factory = this.image.ParserFactories[moduleName + "." + languageName];

        }

        internal Language(Assembly assm, string moduleName, string languageName)

            : this(MImage.LoadFromAssembly(assm), moduleName, languageName)
        {

        }


        public MImage Image { get { return this.image; } }

        public string ModuleName { get { return moduleName; } }

        public string LanguageName { get { return languageName; } }


        public static Language Load(MImage image, string moduleName, string languageName)
        {

            return new Language(image, moduleName, languageName);

        }

        public static Language Load(Assembly assm, string moduleName, string languageName)
        {

            return new Language(assm, moduleName, languageName);

        }

        public static Language LoadFromCurrentAssembly(string moduleName, string languageName)
        {

            var assm = Assembly.GetCallingAssembly();

            return new Language(assm, moduleName, languageName);

        }


        public /*by Vipul dynamic*/ object ParseString(string text)
        {

            return Parse(new StringReader(text));

        }

        public dynamic ParseString(string text, ErrorReporter errors)
        {

            return Parse(new StringReader(text), errors);

        }

        public dynamic ParseString(string text, ErrorReporter errors, string fileName)
        {

            return Parse(new StringReader(text), errors, fileName);

        }

        public dynamic ParseString(string text, ErrorReporter errors, string fileName, SourcePoint startLocation)
        {

            return Parse(new StringReader(text), errors, fileName, startLocation);

        }


        public /*by Vipul*/dynamic Parse(TextReader reader)
        {

            return Parse(new TextReaderTextStream(reader), ErrorReporter.Standard);

        }

        public dynamic Parse(TextReader reader, ErrorReporter errors)
        {

            return Parse(new TextReaderTextStream(reader), errors);

        }

        public dynamic Parse(TextReader reader, ErrorReporter errors, string fileName, SourcePoint startLocation)
        {

            return Parse(new TextReaderTextStream(fileName, reader), errors, startLocation);

        }

        public dynamic Parse(TextReader reader, ErrorReporter errors, string fileName)
        {

            return Parse(new TextReaderTextStream(fileName, reader), errors);

        }

        public /*by Vipul dynamic*/ object Parse(ITextStream stream, ErrorReporter errors)
        {

            var parser = CreateParser();

            var result = parser.Parse(stream, errors);

            //by Vipul return NormalizeResult(result);
            return result;//by Vipul

        }

        public dynamic Parse(ITextStream stream, ErrorReporter errors, SourcePoint startLocation)
        {

            var parser = CreateParser();

            var result = parser.Parse(stream, errors, startLocation);

            return NormalizeResult(result);

        }


        System.Dataflow.Parser CreateParser()
        {

            var parser = factory.Create();

            parser.GraphBuilder = new NodeGraphBuilder(store);

            return parser;

        }


        dynamic NormalizeResult(object result)
        {

            if (result is Node)

                return DynamicObjectNode.NodeToObject((Node)result);

            return result;

        }


    }

}